using System;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;

namespace Atomic.Structures
{
	[Serializable]
	public class LatticeRepeatMatrix
	{
		private int[,] entries;
		[NonSerialized] private int hashCode;

		public LatticeRepeatMatrix(int a11, int a22, int a33)
			: this(a11, 0, 0, 0, a22, 0, 0, 0, a33)
		{
		}

		public LatticeRepeatMatrix(int a11, int a12, int a13, int a21, int a22, int a23, int a31, int a32, int a33)
			: this(new int[,] {{ a11, a12, a13 }, { a21, a22, a23 }, { a31, a32, a33 }})
		{
		}

		public LatticeRepeatMatrix(int[,] entries)
		{
			if (entries == null)
			{
				throw new ArgumentNullException();
			}

			if (entries.GetLength(0) != 3 || entries.GetLength(1) != 3)
			{
				throw new ArgumentException("Invalid dimension.");
			}

			this.entries = (int[,])entries.Clone();
		}

		public LatticeRepeatMatrix(SpaceMatrix a)
			: this(a, 1.0e-5)
		{
		}

		public LatticeRepeatMatrix(SpaceMatrix a, double tol)
			: this((int)Math.Round(a[0, 0]), (int)Math.Round(a[0, 1]), (int)Math.Round(a[0, 2]), (int)Math.Round(a[1, 0]), (int)Math.Round(a[1, 1]), (int)Math.Round(a[1, 2]),  (int)Math.Round(a[2, 0]), (int)Math.Round(a[2, 1]), (int)Math.Round(a[2, 2]))
		{
			for (int i = 0; i < 3; i++)
			{
				for (int j = 0; j < 3; j++)
				{
					if (Math.Abs(a[i, j] - (int)Math.Round(a[i, j])) > tol)
					{
						throw new ArgumentException("No integer scaling matrix representation.");
					}
				}
			}
		}

		public static LatticeRepeatMatrix Create(Structure baseStructure, Structure superStructure)
		{
			return Create(baseStructure, superStructure, 1.0e-5);
		}

		public static LatticeRepeatMatrix Create(Structure baseStructure, Structure superStructure, double tol)
		{
			// The supercell relative to the lattice vectors of the primitive cell. 
			// http://phonopy.sourceforge.net/setting-tags.html#dim
			return new LatticeRepeatMatrix(baseStructure.Lattice.InversePhysicalMap * superStructure.Lattice.PhysicalMap, tol);
		}

		public bool Equals(LatticeRepeatMatrix other)
		{
			if (object.ReferenceEquals(other, null))
			{
				return false;
			}

			return EqualityHelper.Equals(this, other, new object[] { this[0, 0], this[0, 1], this[0, 2], this[1, 0], this[1, 1], this[1, 2], this[2, 0], this[2, 1], this[2, 2] }, new object[] { other[0, 0], other[0, 1], other[0, 2], other[1, 0], other[1, 1], other[1, 2], other[2, 0], other[2, 1], other[2, 2] }, hashCode, other.hashCode);
		}

		public override bool Equals(object other)
		{
			return Equals(other as LatticeRepeatMatrix);
		}

		public override int GetHashCode()
		{
			if (hashCode == 0)
			{
				hashCode = EqualityHelper.HashCode(new object[] { this[0, 0], this[0, 1], this[0, 2], this[1, 0], this[1, 1], this[1, 2], this[2, 0], this[2, 1], this[2, 2] });
			}

			return hashCode;
		}

		public static implicit operator SpaceMatrix(LatticeRepeatMatrix a)
		{
			return new SpaceMatrix(a[0, 0], a[0, 1], a[0, 2], a[1, 0], a[1, 1], a[1, 2], a[2, 0], a[2, 1], a[2, 2]);
		}

		public static explicit operator LatticeRepeatMatrix(SpaceMatrix a)
		{
			return new LatticeRepeatMatrix((int)a[0, 0], (int)a[0, 1], (int)a[0, 2], (int)a[1, 0], (int)a[1, 1], (int)a[1, 2], (int)a[2, 0], (int)a[2, 1], (int)a[2, 2]);
		}

		public static Lattice operator *(Lattice lattice, LatticeRepeatMatrix scaling)
		{
			// Only this operator in the right order (i.e. lattice first) is supported.

			// The lattice vectors of the supercell is defined as column vectors in the relation
			// (as,bs,cs)=(a,b,c)*M, like described here:
			// http://phonopy.sourceforge.net/setting-tags.html#dim

			Basis basis = Basis.CommonBasis(lattice.A.Basis, lattice.B.Basis, lattice.C.Basis);
			SpaceVector a = basis.Decompose(lattice.A).Coordinates;
			SpaceVector b = basis.Decompose(lattice.B).Coordinates;
			SpaceVector c = basis.Decompose(lattice.C).Coordinates;

			return new Lattice(basis, a * scaling[0, 0] + b * scaling[1, 0] + c * scaling[2, 0], a * scaling[0, 1] + b * scaling[1, 1] + c * scaling[2, 1], a * scaling[0, 2] + b * scaling[1, 2] + c * scaling[2, 2]);
		}

		public int this[int row, int column]
		{
			get
			{
				return entries[row, column];
			}
		}

		public static bool operator ==(LatticeRepeatMatrix a1, LatticeRepeatMatrix a2)
		{
			if (object.ReferenceEquals(a1, null))
			{
				if (object.ReferenceEquals(a2, null))
				{
					return true;
				}

				return false;
			}

			return a1.Equals(a2);
		}

		public static bool operator !=(LatticeRepeatMatrix a1, LatticeRepeatMatrix a2)
		{
			return !(a1 == a2);
		}
	}
}
